﻿// Copyright (c) .NET Foundation and Contributors. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

namespace Roslynator.CSharp.Analysis;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UseGenericEventHandlerAnalyzer : BaseDiagnosticAnalyzer
{
    private static readonly MetadataName System_Windows_RoutedEventHandler = MetadataName.Parse("System.Windows.RoutedEventHandler");

    private static ImmutableArray<DiagnosticDescriptor> _supportedDiagnostics;

    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
    {
        get
        {
            if (_supportedDiagnostics.IsDefault)
                Immutable.InterlockedInitialize(ref _supportedDiagnostics, DiagnosticRules.UseGenericEventHandler);

            return _supportedDiagnostics;
        }
    }

    public override void Initialize(AnalysisContext context)
    {
        base.Initialize(context);

        context.RegisterSymbolAction(f => AnalyzeEvent(f), SymbolKind.Event);
    }

    private static void AnalyzeEvent(SymbolAnalysisContext context)
    {
        var eventSymbol = (IEventSymbol)context.Symbol;

        if (eventSymbol.IsImplicitlyDeclared)
            return;

        if (eventSymbol.IsOverride)
            return;

        if (!eventSymbol.ExplicitInterfaceImplementations.IsDefaultOrEmpty)
            return;

        var namedType = eventSymbol.Type as INamedTypeSymbol;

        if (namedType?.Arity != 0)
            return;

        if (namedType.HasMetadataName(MetadataNames.System_EventHandler))
            return;

        if (namedType.HasMetadataName(System_Windows_RoutedEventHandler))
            return;

        IMethodSymbol delegateInvokeMethod = namedType.DelegateInvokeMethod;

        if (delegateInvokeMethod is null)
            return;

        if (!delegateInvokeMethod.ReturnType.IsVoid())
            return;

        ImmutableArray<IParameterSymbol> parameters = delegateInvokeMethod.Parameters;

        if (parameters.Length != 2)
            return;

        if (!parameters[0].Type.IsObject())
            return;

        if (parameters[1].Type.IsRefLikeType)
            return;

        if (eventSymbol.ImplementsInterfaceMember<IEventSymbol>(allInterfaces: true))
            return;

        SyntaxNode node = eventSymbol.GetSyntax(context.CancellationToken);

        TypeSyntax type = GetTypeSyntax(node);

        if (type is null)
            return;

        DiagnosticHelpers.ReportDiagnostic(context, DiagnosticRules.UseGenericEventHandler, type);
    }

    private static TypeSyntax GetTypeSyntax(SyntaxNode node)
    {
        switch (node)
        {
            case EventDeclarationSyntax eventDeclaration:
            {
                return eventDeclaration.Type;
            }
            case VariableDeclaratorSyntax declarator:
            {
                if (declarator.Parent is VariableDeclarationSyntax declaration)
                    return declaration.Type;

                SyntaxDebug.Fail(declarator.Parent);
                break;
            }
            default:
            {
                SyntaxDebug.Fail(node);
                break;
            }
        }

        return null;
    }
}
